package com.beta.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.framework.service.impl.SuperServiceImpl;
import com.beta.constant.CommonConstant;
import com.beta.dto.order.OrderItemDto;
import com.beta.dto.pay.PayRequestDto;
import com.beta.dto.pay.PaymentDto;
import com.beta.em.order.PayStatus;
import com.beta.em.pay.PayRoutes;
import com.beta.entity.Orders;
import com.beta.entity.Payments;
import com.beta.entity.Users;
import com.beta.event.order.OrderEvent;
import com.beta.event.order.OrderPayloadEvent;
import com.beta.mapper.PaymentsMapper;
import com.beta.service.*;
import com.beta.service.pay.PayRouteService;
import com.beta.util.RandomUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 *
 * Payments 表数据服务层接口实现类
 *
 */
@Service
public class PaymentsService extends SuperServiceImpl<PaymentsMapper, Payments> implements IPaymentsService {

    @Autowired
    private IUsersService usersService;
    @Autowired
    private IOrdersService ordersService;
    @Autowired
    private OrderItemsService orderItemsService;
    @Autowired
    private PayRouteService payRouteService;
    @Autowired
    private IDateTimeService dateTimeService;
    @Autowired
    private IOrderStatusService orderStatusService;
    @Autowired
    private PublishService publishService;

    @Override
    public PaymentDto executePayOrder(Integer userId, Integer orderId, PayRoutes payRoutes, String clientIp) {
        Users user = usersService.getAndValidateUser(userId);
        Orders order = ordersService.validateOrder(orderId);
        if (orderStatusService.isExpired(order)) {
            throw new ServiceException("E40010");
        }

        Payments payments = getPayment(user, order, payRoutes, clientIp);
        PaymentDto paymentDto = new PaymentDto();
        paymentDto.setPayUrl(payments.getUrl());
        paymentDto.setOrderCode(order.getCode());
        paymentDto.setAmount(payments.getAmount());

        publishService.publishApplicationEvent(new OrderPayloadEvent(OrderEvent.PAYMENT, order));
        return paymentDto;
    }

    @Override
    public Payments getPaymentByOrderId(Integer orderId) {
        Payments whereEntity = new Payments().setOrderId(orderId);
        return selectOne(whereEntity);
    }

    @Override
    public BigDecimal getPaymentFeeRatioByOrderId(Integer orderId) {
        Payments payments = getPaymentByOrderId(orderId);
        return calcPaymentFeeRate(payments);
    }

    @Override
    public BigDecimal getPaymentFeeByOrderId(Integer orderId) {
        Payments payments = getPaymentByOrderId(orderId);
        BigDecimal amount = payments.getAmount();
        BigDecimal payFeeRate = calcPaymentFeeRate(payments);
        return amount.multiply(payFeeRate).setScale(2, CommonConstant.ROUNDING_MODE);
    }

    private void validateOrder(Integer orderId) {
        if (Objects.isNull(orderId)) {
            throw new ServiceException("E40001");
        }
    }

    private BigDecimal calcPaymentFeeRate(Payments payments) {
        BigDecimal payFeeRate = payRouteService.getTradeFeeRate(PayRoutes.getPayRoutes(payments.getPayRoutes()));
        return payFeeRate;
    }

//    @Override
//    public Payments updatePaymentStatus(String paymentCode, PayStatus payStatus) {
//        Assert.hasText(paymentCode);
//        Assert.notNull(payStatus);
//        Payments whereEntity = new Payments().setCode(paymentCode);
//        updateSelective(new Payments().setStatus(payStatus.getValue()), whereEntity);
//        return selectOne(whereEntity);
//    }

    private Payments getPayment(Users user, Orders order, PayRoutes payRoutes, String clientIp) {
        Payments payments = selectOne(new Payments().setOrderId(order.getId()));
        if (Objects.isNull(payments)) {
            return createPayment(user, order, payRoutes, clientIp);
        }
        if (canContinueUse(payments, payRoutes)) {
            return payments;
        }
        return reCreatePayment(payments, user, order, payRoutes, clientIp);
    }

    private boolean canContinueUse(Payments payments, PayRoutes payRoutes) {
        Date dbDateNow = dateTimeService.toDbDate();
        // 支付方式一致，并且还未过期，则可以继续使用预支付链接
        if (StringUtils.equals(payments.getPayRoutes(), payRoutes.value()) && payments.getExpiresAt().after(dbDateNow)) {
            return true;
        } else {
            return false;
        }
    }

    private Payments createPayment(Users user, Orders order, PayRoutes payRoutes, String clientIp) {
        Date dbDate = dateTimeService.toDbDate();
        Payments payments = new Payments();
        payments.setOrderId(order.getId());
        payments.setStatus(PayStatus.UNPAID.getValue());
        payments.setAmount(order.getAmount());
        payments.setContent(buildPaymentContent(order));
        payments.setCode(buildPaymentCode(null, order));
        payments.setPayRoutes(payRoutes.value());
        payments.setCreatedAt(dbDate);
        payments.setUpdatedAt(dbDate);
        payments.setExpiresAt(calcPaymentExpireDate(payRoutes, dbDate));
        String payResult = invokePayRequest(payments, payRoutes, user, clientIp);
        handlePayResult(payResult, payments, payRoutes);
        insert(payments);
        return payments;
    }

    private Payments reCreatePayment(Payments payments, Users user, Orders order, PayRoutes payRoutes, String clientIp) {
        Date dbDate = dateTimeService.toDbDate();
        payments.setCode(buildPaymentCode(payments, order));
        payments.setOrderId(order.getId());
        payments.setStatus(PayStatus.UNPAID.getValue());
        payments.setUpdatedAt(dbDate);
        payments.setExpiresAt(calcPaymentExpireDate(payRoutes, dbDate));
        String payResult = invokePayRequest(payments, payRoutes, user, clientIp);
        handlePayResult(payResult, payments, payRoutes);
        updateSelective(payments, new Payments().setId(payments.getId()));
        return payments;
    }

    private void handlePayResult(String payResult, Payments payments, PayRoutes payRoutes) {
        if (PayRoutes.YEEPAY.equals(payRoutes) || PayRoutes.WEPAY.equals(payRoutes) || PayRoutes.ALIPAY.equals(payRoutes)) {
            payments.setUrl(JSON.parseObject(payResult).getString("payUrl"));
        } else {
            throw new ServiceException("暂不支持");
        }
    }

    private String invokePayRequest(Payments payments, PayRoutes payRoutes, Users currentUser, String clientIp) {
        PayRequestDto payRequestDto = new PayRequestDto();
        payRequestDto.setPaymentCode(payments.getCode());
        payRequestDto.setAmount(payments.getAmount());
        payRequestDto.setInvalidMinutes(getPayExpireMinutes(payRoutes));
        payRequestDto.setPayRoutes(payRoutes);
        String productInfo = StringUtils.left(payments.getContent(), 49);
        payRequestDto.setProductName(productInfo);
        payRequestDto.setProductDesc(productInfo);
        payRequestDto.setUserCode(currentUser.getCode());
        payRequestDto.setClientIp(clientIp);
        return payRouteService.getPayReturnString(payRequestDto);
    }

    private String buildPaymentContent(Orders order) {
        List<OrderItemDto> orderItems = orderItemsService.getOrdersItemsByOrderId(order.getId());
        if (CollectionUtils.isEmpty(orderItems)) {
            return "";
        }
        String content = orderItems.stream().map(orderItem -> {
            return String.join(",",
                    Objects.toString(orderItem.getProductItemName(), ""),
                    Objects.toString(orderItem.getProductItemText(), ""),
                    Objects.toString(orderItem.getQuantity() + orderItem.getChargeUnit()));
        }).collect(Collectors.joining(";"));
        return StringUtils.left(content, 255);
    }

    /**
     * 生成支付code
     * @param payments
     * @param order
     * @return {orderId}-{订单关联的支付记录的个数累加}-{randCode4}
     */
    private String buildPaymentCode(Payments payments, Orders order) {
        int payCount = 1;
        if (Objects.nonNull(payments)) {
            String[] codeArray = StringUtils.split(payments.getCode(), "-");
            payCount = Integer.parseInt(codeArray[1]);
            payCount++;
        }
        return String.join("-", order.getCode(), String.format("%02d", payCount), RandomUtils.randomStringWithLength(4));
    }

    private Date calcPaymentExpireDate(PayRoutes payRoutes, Date dbDateNow) {
        Long payExpireMinutes = getPayExpireMinutes(payRoutes);
        return Date.from(dbDateNow.toInstant().plus(Duration.ofMinutes(payExpireMinutes)));
    }

    private Long getPayExpireMinutes(PayRoutes payRoutes) {
        return payRouteService.getPayExpireMinutes(payRoutes);
    }

}